package service

import (
	"context"
	"github.com/ricejson/gotool/queue"
	"math"
	"webook/internal/domain"
	"webook/internal/repository"
)

// RankingService 热榜排名业务
type RankingService interface {
	// GetTopN 获取榜单前N名
	GetTopN(ctx context.Context) ([]domain.Article, error)
	// TopN 计算榜单前N名
	TopN(ctx context.Context, n int) error
	topN(ctx context.Context, n int) ([]domain.Article, error)
}

type BatchRankingService struct {
	repo      repository.RankingRepository
	artSvc    IArticleService
	intrSvc   IInterActiveService
	scoreFunc func(p int64, t int64, g float64) float64
	batchSize int
	biz       string
}

func NewBatchRankingService(artSvc IArticleService, intrSvc IInterActiveService, repo repository.RankingRepository) RankingService {
	return &BatchRankingService{
		artSvc:  artSvc,
		intrSvc: intrSvc,
		scoreFunc: func(p int64, t int64, g float64) float64 {
			return float64(p-1) / math.Pow(float64(t+2), g)
		},
		batchSize: 10,
		biz:       "article",
		repo:      repo,
	}
}

func (svc *BatchRankingService) GetTopN(ctx context.Context) ([]domain.Article, error) {
	// 获取TopN数据
	return svc.repo.GetTopN(ctx)
}

func (svc *BatchRankingService) TopN(ctx context.Context, n int) error {
	// 1. 计算topN
	arts, err := svc.topN(ctx, n)
	if err != nil {
		return err
	}
	// 2. 放入缓存中
	return svc.repo.ReplaceTopN(ctx, arts)
}

func (svc *BatchRankingService) topN(ctx context.Context, n int) ([]domain.Article, error) {
	type ScoreArt struct {
		score float64
		art   domain.Article
	}
	pq := queue.NewPriorityQueue[ScoreArt](n, func(e1, e2 ScoreArt) int {
		if e1.score > e2.score {
			return 1
		} else if e1.score == e2.score {
			return 0
		} else {
			return -1
		}
	})
	var offset = 0
	for {
		// 1. 调用artSvc批次取出文章数据
		arts, err := svc.artSvc.ListPub(ctx, offset, svc.batchSize)
		if err != nil {
			return []domain.Article{}, err
		}
		var ids = make([]int64, 0, len(arts))
		for _, art := range arts {
			ids = append(ids, art.Id)
		}
		// 2. 调用intrSvc获取对应文章的交互数据
		intrMap, err := svc.intrSvc.ListByBizIds(ctx, svc.biz, ids)
		// 3. 计算当前批次的得分数
		for _, art := range arts {
			intr, ok := intrMap[art.Id]
			if !ok {
				// 没有这个文章的点赞记录（默认不是topN）
				continue
			}
			// 计算得分
			var scoreArt = ScoreArt{
				score: svc.scoreFunc(intr.LikeCnt, intr.Utime, 1.5),
				art:   art,
			}
			// 如果优先级队列都不足n个，直接加入
			if pq.Size() < n {
				pq.Offer(scoreArt)
			} else {
				// 取出最小的文章进行比较
				top, _ := pq.Peek()
				if scoreArt.score > top.score {
					pq.Poll()
					pq.Offer(scoreArt)
				}
			}
		}
		// 4. 进行下一批次
		if len(arts) < svc.batchSize {
			break
		}
		offset += len(arts)
	}
	// 最终排序
	var count = pq.Size()
	var resArts = make([]domain.Article, count)
	for i := count - 1; i >= 0; i-- {
		scoreArt, _ := pq.Poll()
		resArts[i] = scoreArt.art
	}
	return resArts, nil
}
